package bsh;

import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

/**
 * Fábrica de interpretadores para execução e checagem de dependências de scripts.
 *
 * @author Cesar Olmos
 */
public class InterpreterFactory {
    private final List<Pattern> forbidden = new ArrayList<Pattern>();

    /**
     * Construtor
     */
    public InterpreterFactory() {
        this.addForbiddenClass("Runtime");
        this.addForbiddenClass("ProcessBuilder");
        this.addForbiddenClass("UNIXProcess");
        this.addForbiddenClass("ProcessImpl");
        this.addForbiddenClass("Process");
    }

    /**
     * Adiciona a classe à lista de proibições em scripts
     *
     * @param simpleClassName simpleClassName
     */
    private void addForbiddenClass(String simpleClassName) {
        String regex = String.format(".*(^|\\s|\\.|;)%s(($|\\s*(\\.|\\())).*", simpleClassName);
        this.forbidden.add(Pattern.compile(regex, Pattern.DOTALL));
    }

    /**
     * Pega do cache ou cria um novo {@link Interpreter}
     *
     * @param initScript String
     * @param script String
     * @return {@link Interpreter}
     * @throws EvalError e
     */
    public Interpreter getInterpreterForExecution(final String initScript, final String script) throws EvalError {
        this.validateScript(script);

        Interpreter interpreter = this.createInterpreter(initScript);

        String copy;
        if (!script.trim().endsWith(";") && !script.trim().endsWith("}")) {
            copy = script + ";";
        } else {
            copy = script;
        }
        interpreter.eval("void executeScript() { " + copy + "}\n");
        return interpreter;
    }

    /**
     * Pega do cache ou cria um novo {@link Interpreter}
     *
     * @param initScript String
     * @param script String
     * @return {@link Interpreter}
     * @throws EvalError e
     */
    public Interpreter getInterpreterForDependencies(final String initScript, final String script) throws EvalError {
        this.validateScript(script);

        Interpreter interpreter = this.createInterpreter(initScript);

        String copy;
        if (!script.trim().endsWith(";") && !script.trim().endsWith("}")) {
            copy = script + ";";
        } else {
            copy = script;
        }

        CallStack call = new CallStack(interpreter.getNameSpace());

        Parser parser = new Parser(new StringReader(copy));

        boolean eof = false;
        while (!eof) {
            eof = parser.Line();
            if (parser.jjtree.nodeArity() > 0) {
                SimpleNode n = (SimpleNode) parser.jjtree.popNode();
                if (n instanceof BSHMethodDeclaration) {
                    n.eval(call, interpreter);
                }
            }
        }

        return interpreter;
    }

    /**
     * Cria um interpretador já realizando a avaliação do script de inicialização.
     *
     * @param initScript o scvript de inicialização.
     * @return o interpretador.
     */
    private Interpreter createInterpreter(final String initScript) {
        Interpreter interpreter = new Interpreter();
        try {
            interpreter.eval(initScript);
        } catch (EvalError e) {
            throw new IllegalArgumentException(e);
        }
        return interpreter;
    }

    /**
     * Valida se um script possuí palavras proibidas
     *
     * @param script o script a ser validado.
     */
    private void validateScript(final String script) {
        for (Pattern forb : this.forbidden) {
            if (forb.matcher(script).matches()) {
                throw new IllegalArgumentException("Forbiden pattern: " + forb.pattern() + " on script");
            }
        }
    }
}
